package ar.com.fiuba.ElectronicCircuitAnalyst.domain.impl;

import java.util.List;

import ar.com.fiuba.ElectronicCircuitAnalyst.domain.api.Component;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.api.Lead;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.api.Output;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.common.Specification;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.exceptions.EvaluationException;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.exceptions.InvalidCircuitElementException;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.exceptions.SimulationException;
import ar.com.fiuba.ElectronicCircuitAnalyst.util.MapDelegate;
import ar.com.fiuba.ElectronicCircuitAnalyst.util.MapFunction;

public class ComponentImpl extends CircuitElementImpl implements Component {

	private String type;
	private List<Output> outputs;
	private Specification<Component> leadsQtySpecification;

	/**
	 * Factory method to get an empty component. This is for repository use
	 * only.
	 * 
	 * @param name
	 *            the name for the desired component.
	 * @return an instance of an empty component with the given name.
	 */
	public static ComponentImpl getEmptyComponent(String name) {
		return new ComponentImpl(name);
	}

	private ComponentImpl(String name) {
		this.name = name;
	}

	/**
	 * Create a new Component.
	 * 
	 * @param name
	 *            the name of the Component
	 * @param type
	 *            the type of the Component
	 * @param inputs
	 *            a list with the input leads of the Component
	 * @param outputs
	 *            a list with the outputs of the Component
	 * @param leadsQtySpecification
	 */
	public ComponentImpl(String name, String type, List<Lead> inputLeads, List<Output> outputs,
			Specification<Component> leadsQtySpecification) throws InvalidCircuitElementException {
		this.name = name;
		this.type = type;
		this.outputs = outputs;
		this.inputLeads = inputLeads;
		this.outputLeads = MapFunction.map(outputs, new MapDelegate<Output, Lead>() {
			public Lead map(Output obj) {
				return obj.getOutputLead();
			}
		});
		this.leadsQtySpecification = leadsQtySpecification;

		validateComponent();
	}

	public void simulate() throws SimulationException {
		List<Boolean> currentInputs = MapFunction.map(this.inputLeads, new MapDelegate<Lead, Boolean>() {
			public Boolean map(Lead obj) {
				return obj.getValue();
			}
		});

		for (Output output : this.outputs) {
			try {
				output.setOutput(currentInputs);
			} catch (EvaluationException e) {
				throw new SimulationException("Error setting a Componente Output" + e.getMessage());
			}
		}
	}

	private void validateComponent() throws InvalidCircuitElementException {
		if (!this.leadsQtySpecification.isSatisfiedBy(this))
			throw new InvalidCircuitElementException("Component doesn't satisfy specification.");
	}

	public String getType() {
		return this.type;
	}

}
